package com.kamistoat.meimeistore.commonsecurity.auth;

import com.kamistoat.meimeistore.apisystem.model.LoginUser;
import com.kamistoat.meimeistore.commoncore.exception.auth.NotLoginException;
import com.kamistoat.meimeistore.commoncore.exception.auth.NotPermissionException;
import com.kamistoat.meimeistore.commoncore.exception.auth.NotRoleException;
import com.kamistoat.meimeistore.commoncore.utils.StringUtils;
import com.kamistoat.meimeistore.commonsecurity.annotation.Logical;
import com.kamistoat.meimeistore.commonsecurity.annotation.RequiresLogin;
import com.kamistoat.meimeistore.commonsecurity.annotation.RequiresPermissions;
import com.kamistoat.meimeistore.commonsecurity.annotation.RequiresRoles;
import com.kamistoat.meimeistore.commonsecurity.utils.SecurityUtils;
import com.kamistoat.meimeistore.commonsecurity.utils.TokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.PatternMatchUtils;

import java.util.ArrayList;
import java.util.Collection;

/**
 * 权限验证的工具类
 */
@Component
public class AuthUtil {
    @Autowired
    TokenUtils tokenUtils;

    /**
     * 具备所有权限的通配符
     */
    private static final String ALL_PERMISSION = "*:*:*";

    /**
     * 超级管理员权限标识
     */
    private static final String SUPER_ADMIN = "admin";

    /**
     * 注销当前会话。从缓存中删除当前会话的用户令牌
     */
    public void logout() {
        // 首先需要获取当前会话的jwt
        String jwtToken = SecurityUtils.getJwtToken();
        if (StringUtils.isNotEmpty(jwtToken)) {
            logout(jwtToken);
        }
    }

    /**
     * 根据jwt注销与其对应的会话，从缓存中删除与之对应的用户令牌。
     * 并不需要去管ThreadLocal中，会因为ThreadLocal只管一次链接线程，这次删了下次肯定就没有了
     *
     * @param jwtToken jwt数据
     */
    public void logout(String jwtToken) {
        tokenUtils.delLoginLoginUser(jwtToken);
        tokenUtils.delCoreLoginUser(jwtToken);
        tokenUtils.delOnlineUser(jwtToken);
    }

    /**
     * 从ThreadLocal中获取 核心信息令牌.
     * 在此之前已经由拦截器从缓存中提取 核心信息令牌转存到ThreadLocal中了
     * 由于验证权限 和 验证角色 的方法都会调用该方法，因此保证了一定是在登录状态下验证权限和验证角色
     *
     * @return 从ThreadLocal中获取的 核心信息令牌
     */
    public LoginUser getThreadLoginUser() throws NotLoginException {
        String jwtToken = SecurityUtils.getJwtToken();
        if (StringUtils.isEmpty(jwtToken)) {
            throw new NotLoginException("当前会话未携带JwtToken信息");
        }
        LoginUser coreLoginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(coreLoginUser)) {
            throw new NotLoginException("无法从ThreadLocal中获取用户令牌");
        }
        return coreLoginUser;
    }

    /**
     * 从缓存中获取 后端登录令牌
     *
     * @param jwtToken jwtToken
     * @return 缓存中的 后端登录令牌
     */
    public LoginUser getCacheLoginLoginUser(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) {
            throw new NotLoginException("当前会话未携带JwtToken信息");
        }
        LoginUser loginLoginUser = tokenUtils.getLoginLoginUser(jwtToken);
        if (StringUtils.isNull(loginLoginUser)) {
            throw new NotLoginException("无法从缓存中获取 后端登录令牌");
        }
        return loginLoginUser;
    }

    /**
     * 从缓存中获取 核心信息令牌。即便为空也没关系，只有登录令牌强行决定是否异常。
     *
     * @param jwtToken jwtToken
     * @return 缓存中的 核心信息令牌
     */
    public LoginUser getCacheCoreLoginUser(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) {
            throw new NotLoginException("当前会话未携带JwtToken信息");
        }
        LoginUser coreLoginUser = tokenUtils.getCoreLoginUser(jwtToken);
//        if (StringUtils.isNull(coreLoginUser)) {
//            throw new NotLoginException("无法从缓存中获取 核心信息令牌");
//        }
        return coreLoginUser;
    }

    /**
     * 验证当前用户有效期, 如果相差不足120分钟，自动刷新缓存
     *
     * @param loginUser 用户令牌
     */
    public void verifyLoginUserExpire(LoginUser loginUser) {
        tokenUtils.verifyToken(loginUser);
    }


    /**
     * 从ThreadLocal中获取当前用户令牌的角色列表
     *
     * @return 角色列表
     */
    public ArrayList<String> getRoleList() {
        try {
            LoginUser loginUser = getThreadLoginUser();
            return (ArrayList<String>) loginUser.getRoles();
        } catch (Exception e) {
            return new ArrayList<>();
        }
    }

    /**
     * 从ThreadLocal中获取当前用户令牌的的权限列表
     *
     * @return 权限列表
     */
    public ArrayList<String> getPermiList() {
        try {
            LoginUser loginUser = getThreadLoginUser();
            return (ArrayList<String>) loginUser.getPermissions();
        } catch (Exception e) {
            return new ArrayList<>();
        }
    }

    /**
     * 从ThreadLocal中检查用户是否登录，若未登录则抛出异常。
     * 异常由 getThreadLoginUser() 方法抛出。严格来说 checkLogin()方法不写也行，因为 getThreadLoginUser()实现相同作用
     */
    public void checkLogin() {
        getThreadLoginUser();
    }

    /**
     * 同样是一个无意义的方法。因为 @RequiresRoles注解并不携带参数
     *
     * @param requiresLogin @requiresLogin注解
     */
    public void checkLogin(RequiresLogin requiresLogin) {
        checkLogin();
    }

    /**
     * 判断一个权限集合中是否包含指定权限。能够判断通用权限 *:* 的情况
     *
     * @param authorities 用户具备的权限列表
     * @param permission  判断用户是否含有该权限
     * @return 用户是否具备某权限
     */
    public boolean hasPermi(Collection<String> authorities, String permission) {
        return authorities.stream().filter(StringUtils::hasText)
                .anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission));
    }

    /**
     * 判断当前 ThreadLocal用户是否具有指定权限
     *
     * @param permission 判断用户是否具有该权限
     * @return bool的判断结果
     */
    public boolean hasPermi(String permission) {
        return hasPermi(getPermiList(), permission);
    }

    /**
     * 判断当前 ThreadLocal用户是否具有指定权限。如果不具备则直接抛出异常
     *
     * @param permission 判断用户是否具有该权限
     * @throws NotPermissionException 无权限异常
     */
    public void checkPermi(String permission) throws NotPermissionException {
        if (!hasPermi(permission)) {
            throw new NotPermissionException(permission);
        }
    }

    /**
     * 根据一个 @RequiresPermissions 注解，判断当前 ThreadLocal用户是否满足注解的要求
     *
     * @param requiresPermissions @RequiresPermissions注解
     */
    public void checkPermi(RequiresPermissions requiresPermissions) {
        // 超级管理员直接放行
        if (!SecurityUtils.curLoginUserIsAdmin()) {
            if (requiresPermissions.logical() == Logical.OR) {
                // OR逻辑检查
                checkPermiOr(requiresPermissions.value());
            } else {
                // AND逻辑检查
                checkPermiAnd(requiresPermissions.value());
            }
        }
    }

    /**
     * 验证当前 ThreadLocal用户是否具有 所有 指定的权限
     *
     * @param permissions 一组权限
     * @throws NotPermissionException 无权限异常
     */
    public void checkPermiAnd(String... permissions) throws NotPermissionException {
        ArrayList<String> permiList = getPermiList();
        for (String permission : permissions) {
            if (!hasPermi(permiList, permission)) {
                throw new NotPermissionException(permission);
            }
        }
    }

    /**
     * 验证当前 ThreadLocal用户是否具有指定的权限 中的任意一个即可
     *
     * @param permissions 一组权限
     * @throws NotPermissionException 无权限异常
     */
    public void checkPermiOr(String... permissions) throws NotPermissionException {
        ArrayList<String> permiList = getPermiList();
        for (String permission : permissions) {
            if (hasPermi(permiList, permission)) {
                return;
            }
        }
        if (permissions.length > 0) {
            throw new NotPermissionException(permissions);

        }
    }

    /**
     * 判断是否包含角色
     *
     * @param roles 用户具备的角色列表
     * @param role  角色
     * @return 用户是否具备某角色权限
     */
    public boolean hasRole(Collection<String> roles, String role) {
        return roles.stream().filter(StringUtils::hasText)
                .anyMatch(x -> SUPER_ADMIN.contains(x) || PatternMatchUtils.simpleMatch(x, role));
    }

    /**
     * 判断当前 ThreadLocal用户是否具有指定角色
     *
     * @param role 判断用户是否含有该角色
     * @return 用户是否具备某角色
     */
    public boolean hasRole(String role) {
        return hasRole(getRoleList(), role);
    }

    /**
     * 判断当前 ThreadLocal用户是否具有指定角色。如果不具备则直接抛出异常
     *
     * @param role 判断用户是否具有该角色
     * @throws NotRoleException 无角色异常
     */
    public void checkRole(String role) throws NotRoleException {
        if (!hasRole(role)) {
            throw new NotRoleException(role);
        }
    }

    /**
     * 根据一个 @RequiresRoles 注解，判断当前 ThreadLocal用户是否满足注解的要求
     *
     * @param requiresRoles @requiresRoles注解
     */
    public void checkRole(RequiresRoles requiresRoles) {
        // 超级管理员直接放行
        if (!SecurityUtils.curLoginUserIsAdmin()) {
            if (requiresRoles.logical() == Logical.AND) {
                checkRoleAnd(requiresRoles.value());
            } else {
                checkRoleOr(requiresRoles.value());
            }
        }
    }


    /**
     * 验证ThreadLocal用户是否含有指定角色，必须全部拥有
     *
     * @param roles 角色标识数组
     */
    public void checkRoleAnd(String... roles) throws NotRoleException {
        ArrayList<String> roleList = getRoleList();
        for (String role : roles) {
            if (!hasRole(roleList, role)) {
                throw new NotRoleException(role);
            }
        }
    }

    /**
     * 验证ThreadLocal用户是否含有指定角色，只需包含其中一个
     *
     * @param roles 角色标识数组
     */
    public void checkRoleOr(String... roles) throws NotRoleException {
        ArrayList<String> roleList = getRoleList();
        for (String role : roles) {
            if (hasRole(roleList, role)) {
                return;
            }
        }
        if (roles.length > 0) {
            throw new NotRoleException(roles);
        }
    }


}
